package lol.clann.bukkit.pluginboot.service;

import lol.clann.bukkit.pluginboot.task.PatternTimer;
import lol.clann.bukkit.pluginboot.util.TaskUtils;
import lol.clann.clannboot.annotation.inject.Inject;
import lol.clann.clannboot.annotation.load.PostLoad;
import lol.clann.clannboot.annotation.svc.Service;
import lol.clann.clannboot.context.ModuleContext;
import lol.clann.clannboot.model.Ref;
import lol.clann.clannboot.module.ConfigRef;
import lol.clann.clannboot.module.Module;
import lol.clann.clannboot.util.BaseUtils;
import lol.clann.clannboot.util.ObjectUtils;
import lol.clann.clannboot.util.collection.MapUtils;
import com.google.common.reflect.TypeToken;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;

/**
 * 负责执行TimerPatternAsync定义的任务
 *
 * @author pyz
 * @date 2018/9/16 下午8:45
 */
@Service(priority = Integer.MAX_VALUE)
public class PatternTimerExecutor {

    private static final Map<Ref<String>, List<Node>> taskList = new WeakHashMap<>();

    private ExecutorService worker = new ThreadPoolExecutor(8, 16, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
    @Inject
    private Module module;
    @Inject
    private TaskUtils taskUtils;
    private DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static void addTask(String moduleId, PatternTimer config, Runnable runnable) {
        Module module = ModuleContext.getModule(moduleId);
        Node task = new Node()
                .setConfig(config)
                .setRunnable(runnable)
                .setModuleId(moduleId)
                .setModule(module);
        for (String ref : config.patterns()) {
            if (!ConfigRef.isConfigRef(ref)) {
                // 直接是值
                task.addPattern(Pattern.compile(ref));
                continue;
            }
            ConfigRef v = ConfigRef.of(moduleId, ref);
            if (!v.isCollection()) {
                // 配置里是值
                task.addPattern(Pattern.compile(v.getValue(String.class)));
                continue;
            }
            // 配置里是集合
            List<String> patterns = v.getValue(new TypeToken<List<String>>() {
            });
            for (String pattern : patterns) {
                task.addPattern(Pattern.compile(pattern));
            }
        }
        List<Node> list = MapUtils.getOrCreate(taskList, Ref.of(moduleId), ArrayList::new);
        list.add(task);
        module.add(() -> task.cancel());
    }

    @PostLoad
    private void load() {
        Thread t = new Thread(() -> {
            while (module.isRunning()) {
                long now = System.currentTimeMillis();
                String time = format.format(new Date());
                for (List<Node> nodes : taskList.values()) {
                    Iterator<Node> it = nodes.iterator();
                    while (it.hasNext()) {
                        Node task = it.next();
                        if (task.getModule() == null || !task.getModule().isRunning()) {
                            it.remove();
                            continue;
                        }
                        for (Pattern pattern : task.getPatterns()) {
                            if (!pattern.matcher(time).find()) {
                                continue;
                            }
                            if (task.getConfig().async()) {
                                // 异步的交给线程池执行
                                worker.execute(task.getRunnable());
                            } else {
                                // 同步的交给bukkit调度到主线程执行
                                taskUtils.runTaskLater(task.getModule().getExtend(), 1, task.getRunnable());
                            }
                        }
                    }
                }
                // 这样保证不会因为长时间累计而缺少执行次数
                long next = now / 1000 * 1000 + 500;
                if (next <= now) {
                    next += 1000;
                }
                BaseUtils.dely(next - now);
            }
        });
        t.setName("TimerPatternWorkerThread");
        t.start();
        module.add(() -> t.interrupt());
    }

    @Setter
    @Getter
    @ToString
    private static class Node {
        private PatternTimer config;
        private String moduleId;
        private Set<Pattern> patterns = new HashSet<>();
        private Runnable runnable;
        private Module module;

        private void cancel() {
            ObjectUtils.destroy(this);
        }

        public Node addPattern(Pattern pattern) {
            patterns.add(pattern);
            return this;
        }
    }
}
